/***************************************************
*		 Copyright (c) 2018 MINE 田宇
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
***************************************************/

#include <Uefi.h> 
#include <Base.h> 
#include <Library/UefiLib.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/PrintLib.h>
#include <Protocol/DiskIo.h>
#include <Protocol/BlockIo.h>
#include <Protocol/DevicePath.h>
#include <Protocol/DevicePathToText.h>
#include <Uefi/UefiGpt.h>
#include <Library/DevicePathLib.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/SimpleFileSystem.h>
#include <Guid/FileInfo.h>

EFI_STATUS PrintNode(EFI_DEVICE_PATH_PROTOCOL * Node)
{
	Print(L"(%d %d)/", Node->Type, Node->SubType);
	return 0;
}

EFI_DEVICE_PATH_PROTOCOL* WalkthroughDevicePath(EFI_DEVICE_PATH_PROTOCOL* DevPath, EFI_STATUS (*Callbk)(EFI_DEVICE_PATH_PROTOCOL*))
{
	EFI_DEVICE_PATH_PROTOCOL* pDevPath = DevPath;
	while(!IsDevicePathEnd (pDevPath))
	{
        	if(Callbk)
		{
			EFI_STATUS Status = Callbk(pDevPath);
			if(Status != 0) 
			{
				if(Status < 0) pDevPath = NULL;
				break;
			}
		}
		pDevPath = NextDevicePathNode (pDevPath); 
	}
	return pDevPath;
}

EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable)
{
	EFI_LOADED_IMAGE        *LoadedImage;
	EFI_DEVICE_PATH         *DevicePath;
	EFI_FILE_IO_INTERFACE   *Vol;
	EFI_FILE_HANDLE         RootFs;
	EFI_FILE_HANDLE         FileHandle;

	int i = 0;
	void (*func)(void);
	EFI_STATUS status = EFI_SUCCESS;

	EFI_DEVICE_PATH_TO_TEXT_PROTOCOL* Device2TextProtocol = 0;

	gBS->LocateProtocol(&gEfiDevicePathToTextProtocolGuid,NULL,(VOID**)&Device2TextProtocol);
	gBS->HandleProtocol(ImageHandle,&gEfiLoadedImageProtocolGuid,(VOID*)&LoadedImage);
	gBS->HandleProtocol(LoadedImage->DeviceHandle,&gEfiDevicePathProtocolGuid,(VOID*)&DevicePath);

	CHAR16* TextDevicePath = Device2TextProtocol->ConvertDevicePathToText(DevicePath,FALSE,TRUE); 
	Print(L"%s\n",TextDevicePath);
	if(TextDevicePath)
		gBS->FreePool(TextDevicePath);
	WalkthroughDevicePath(DevicePath,PrintNode); 
	Print(L"\n");

	gBS->HandleProtocol(LoadedImage->DeviceHandle,&gEfiSimpleFileSystemProtocolGuid,(VOID*)&Vol);
	Vol->OpenVolume(Vol,&RootFs);
	RootFs->Open(RootFs,&FileHandle,(CHAR16*)L"kernel.bin",EFI_FILE_MODE_READ,0);

	EFI_FILE_INFO* FileInfo;
	UINTN BufferSize = 0;
	EFI_PHYSICAL_ADDRESS pages = 0x100000;

	BufferSize = sizeof(EFI_FILE_INFO) + sizeof(CHAR16) * 100;
	gBS->AllocatePool(EfiBootServicesData,BufferSize,(VOID**)&FileInfo); 
	FileHandle->GetInfo(FileHandle,&gEfiFileInfoGuid,&BufferSize,FileInfo);
	Print(L"\tFileName:%s\t Size:%d\t FileSize:%d\t Physical Size:%d\n",FileInfo->FileName,FileInfo->Size,FileInfo->FileSize,FileInfo->PhysicalSize);

	Print(L"Read kernel file to memory\n");
	gBS->AllocatePages(AllocateAddress,EfiConventionalMemory,(FileInfo->FileSize + 0x1000 - 1) / 0x1000,&pages);
	BufferSize = FileInfo->FileSize;
	FileHandle->Read(FileHandle,&BufferSize,(VOID*)pages);
	gBS->FreePool(FileInfo);
	FileHandle->Close(FileHandle);
	RootFs->Close(RootFs);

	gBS->CloseProtocol(LoadedImage->DeviceHandle,&gEfiSimpleFileSystemProtocolGuid,ImageHandle,NULL);
	gBS->CloseProtocol(LoadedImage->DeviceHandle,&gEfiDevicePathProtocolGuid,ImageHandle,NULL);
	gBS->CloseProtocol(ImageHandle,&gEfiLoadedImageProtocolGuid,ImageHandle,NULL);
	gBS->CloseProtocol(Device2TextProtocol,&gEfiDevicePathToTextProtocolGuid,ImageHandle,NULL);

	EFI_GRAPHICS_OUTPUT_PROTOCOL* gGraphicsOutput = 0;
	EFI_GRAPHICS_OUTPUT_MODE_INFORMATION* Info = 0;
	UINTN InfoSize = 0;

	gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid,NULL,(VOID **)&gGraphicsOutput);
	Print(L"Current Mode:%02d,Version:%x,Format:%d,Horizontal:%d,Vertical:%d,ScanLine:%d,FrameBufferBase:%010lx,FrameBufferSize:%010lx\n",gGraphicsOutput->Mode->Mode,gGraphicsOutput->Mode->Info->Version,gGraphicsOutput->Mode->Info->PixelFormat,gGraphicsOutput->Mode->Info->HorizontalResolution,gGraphicsOutput->Mode->Info->VerticalResolution,gGraphicsOutput->Mode->Info->PixelsPerScanLine,gGraphicsOutput->Mode->FrameBufferBase,gGraphicsOutput->Mode->FrameBufferSize);

	long H_V_Resolution = gGraphicsOutput->Mode->Info->HorizontalResolution * gGraphicsOutput->Mode->Info->VerticalResolution;
	int MaxResolutionMode = gGraphicsOutput->Mode->Mode;

	for(i = 0;i < gGraphicsOutput->Mode->MaxMode;i++)
	{
		Print(L"Mode:%02d\t",i);
		gGraphicsOutput->QueryMode(gGraphicsOutput,i,&InfoSize,&Info);
/*		if(Info->PixelFormat == 1)
			Print(L"Version:%x,Format:%d,Horizontal:%d,Vertical:%d,ScanLine:%d\n",Info->Version,Info->PixelFormat,Info->HorizontalResolution,Info->VerticalResolution,Info->PixelsPerScanLine);
		else
			continue;
*/
		if(Info->HorizontalResolution * Info->VerticalResolution > H_V_Resolution)
		{
			H_V_Resolution = Info->HorizontalResolution * Info->VerticalResolution;
			MaxResolutionMode = i;
		}
		gBS->FreePool(Info);
	}

	gGraphicsOutput->SetMode(gGraphicsOutput,MaxResolutionMode);
	gGraphicsOutput->QueryMode(gGraphicsOutput,MaxResolutionMode,&InfoSize,&Info);
	Print(L"Set Mode:%02d,Version:%x,Format:%d,Horizontal:%d,Vertical:%d,ScanLine:%d\n",MaxResolutionMode,Info->Version,Info->PixelFormat,Info->HorizontalResolution,Info->VerticalResolution,Info->PixelsPerScanLine);
	gBS->FreePool(Info);
	gBS->LocateProtocol(&gEfiGraphicsOutputProtocolGuid,NULL,(VOID **)&gGraphicsOutput);
	Print(L"Current Mode:%02d,Version:%x,Format:%d,Horizontal:%d,Vertical:%d,ScanLine:%d,FrameBufferBase:%010lx,FrameBufferSize:%010lx\n",gGraphicsOutput->Mode->Mode,gGraphicsOutput->Mode->Info->Version,gGraphicsOutput->Mode->Info->PixelFormat,gGraphicsOutput->Mode->Info->HorizontalResolution,gGraphicsOutput->Mode->Info->VerticalResolution,gGraphicsOutput->Mode->Info->PixelsPerScanLine,gGraphicsOutput->Mode->FrameBufferBase,gGraphicsOutput->Mode->FrameBufferSize);

	gBS->SetMem((void*)0x59000,sizeof(*gGraphicsOutput->Mode) + gGraphicsOutput->Mode->SizeOfInfo,0);
	gBS->CopyMem((void*)0x59000,(void*)gGraphicsOutput->Mode,sizeof(*gGraphicsOutput->Mode));
	gBS->CopyMem((void*)(0x59000 + sizeof(*gGraphicsOutput->Mode)),(void*)gGraphicsOutput->Mode->Info,gGraphicsOutput->Mode->SizeOfInfo);

	long * PageTableEntry = (long *)0x103000;
	for(i = 0;i < gGraphicsOutput->Mode->FrameBufferSize >> 21;i++)
	{
		*(PageTableEntry + 24 + i) = gGraphicsOutput->Mode->FrameBufferBase | 0x200000 * i | 0x83;
		Print(L"page %02d,%018lx:%018lx\n",i,*(PageTableEntry + 24 + i),(long)(PageTableEntry + 24 + i));
	}

	gBS->CloseProtocol(gGraphicsOutput,&gEfiGraphicsOutputProtocolGuid,ImageHandle,NULL);

	struct EFI_E820_MEMORY_DESCRIPTOR
	{
		unsigned long address;
		unsigned long length;
		unsigned int  type;
	}__attribute__((packed));

	struct EFI_E820_MEMORY_DESCRIPTOR *e820p = (void*)0x5a000;
	UINTN MemMapSize = 0;
	EFI_MEMORY_DESCRIPTOR* MemMap = 0;
	UINTN MapKey = 0;
	UINTN DescriptorSize = 0;
	UINT32 DesVersion = 0;

	Print(L"Get EFI_MEMORY_DESCRIPTOR Structure\n");
	gBS->GetMemoryMap(&MemMapSize,MemMap,&MapKey,&DescriptorSize,&DesVersion);
	gBS->AllocatePool(EfiRuntimeServicesData,MemMapSize,(VOID**)&MemMap);
	gBS->GetMemoryMap(&MemMapSize,MemMap,&MapKey,&DescriptorSize,&DesVersion);

	gBS->SetMem((void*)e820p,MemMapSize,0);
	for( i = 0; i< MemMapSize / (DescriptorSize); i++)
	{
		EFI_MEMORY_DESCRIPTOR* MMap = (EFI_MEMORY_DESCRIPTOR*) ( ((CHAR8*)MemMap) + i * DescriptorSize);
//		Print(L"MemoryMap %4d %10d (%10lx<->%10lx) %016lx\n",MMap->Type,MMap->NumberOfPages,MMap->PhysicalStart,MMap->VirtualStart,MMap->Attribute);
		e820p->address = MMap->PhysicalStart;
		e820p->length = MMap->NumberOfPages << 12;
		e820p->type = MMap->Type;
		e820p++;
	}
	gBS->FreePool(MemMap);

	Print(L"Call ExitBootServices And Jmp to Kernel\n");
	gBS->GetMemoryMap(&MemMapSize,MemMap,&MapKey,&DescriptorSize,&DesVersion);
	gBS->AllocatePool(EfiRuntimeServicesData,MemMapSize,(VOID**)&MemMap);
	gBS->GetMemoryMap(&MemMapSize,MemMap,&MapKey,&DescriptorSize,&DesVersion);
	status = gBS->ExitBootServices(ImageHandle,MapKey);
	if(EFI_ERROR(status))
	{
		Print(L"ExitBootSvcs: failed, memory map has changed.\n");
		return EFI_INVALID_PARAMETER;
	}
	gRT->SetVirtualAddressMap(MemMapSize,DescriptorSize,DesVersion,MemMap);

//	Print(L"Jmp to Kernel\n");
	func = (void *)0x100000;
	func();

	return EFI_SUCCESS;
}

